#include <utils/image_loader.h>

#include "texture.h"

#include <glad/glad.h>

#include <glog/logging.h>

namespace csugl {

    TextureWrap::TextureWrap(TextureWrapType s_type, TextureWrapType t_type)
    {
        switch (s_type)
        {
            case TextureWrapType::REPEAT: _s_type = GL_REPEAT; break;
            case TextureWrapType::MIRRORED_REPEAT: _s_type = GL_MIRRORED_REPEAT; break;
            case TextureWrapType::CLAMP_TO_EDGE: _s_type = GL_CLAMP_TO_EDGE; break;
            case TextureWrapType::CLAMP_TO_BORDER: _s_type = GL_CLAMP_TO_BORDER; break;
            default: _s_type = GL_REPEAT; break;
        }
        switch (t_type)
        {
            case TextureWrapType::REPEAT: _t_type = GL_REPEAT; break;
            case TextureWrapType::MIRRORED_REPEAT: _t_type = GL_MIRRORED_REPEAT; break;
            case TextureWrapType::CLAMP_TO_EDGE: _t_type = GL_CLAMP_TO_EDGE; break;
            case TextureWrapType::CLAMP_TO_BORDER: _t_type = GL_CLAMP_TO_BORDER; break;
            default: _t_type = GL_REPEAT; break;
        }
    }

    TextureFilter::TextureFilter(TextureFilterType min_type, TextureFilterType mag_type)
    {
        switch (min_type)
        {
            case NEAREST: _min_type = GL_NEAREST; break;
            case LINEAR: _min_type = GL_LINEAR; break;
            case N_SAMPLE | N_INTER: _min_type = GL_NEAREST_MIPMAP_NEAREST; break;
            case N_SAMPLE | L_INTER: _min_type = GL_NEAREST_MIPMAP_LINEAR; break;
            case L_SAMPLE | N_INTER: _min_type = GL_LINEAR_MIPMAP_NEAREST; break;
            case L_SAMPLE | L_INTER: _min_type = GL_LINEAR_MIPMAP_LINEAR; break;
            default: LOG(ERROR) << "Unknown Minified Texture Parameter";
        }
        switch (mag_type)
        {
            case NEAREST: _mag_type = GL_NEAREST; break;
            case LINEAR: _mag_type = GL_LINEAR; break;
            case N_SAMPLE | N_INTER: 
            case N_SAMPLE | L_INTER: 
            case L_SAMPLE | N_INTER: 
            case L_SAMPLE | L_INTER: LOG(ERROR) << "Magnified Texture cannot set Mipmap"; break;
            default: LOG(ERROR) << "Unknown Magnified Texture Parameter";
        }
    }
    
    // Texture2D::Texture2D(int width, int height, 
    //     int channel, const uint8_t* data)
    //         : Texture(width, height, channel) {
    //     glGenTextures(1, &tex_ID);
    //     bind();
    //     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    //     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    //     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    //     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    //     if (channel == 3) {
    //         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
    //     } else if (channel == 4) {
    //         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    //     } else if (channel == 1) {
    //         glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, data);
    //     }
    //     glGenerateMipmap(GL_TEXTURE_2D);
    // }

    Texture2D::Texture2D(Texture2DProp prop, const uint8_t *data)
        : Texture(prop.width, prop.height, prop.channel)
    {
        glGenTextures(1, &tex_ID);
        bind();
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, prop.param.wrap._s_type);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, prop.param.wrap._t_type);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, prop.param.filter._min_type);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, prop.param.filter._mag_type);
        if (channel == 3) {
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
        } else if (channel == 4) {
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
        } else if (channel == 1) {
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, data);
        }
        if (prop.param.gen_mipmap)
            glGenerateMipmap(GL_TEXTURE_2D);
    }

    void Texture2D::bind() const {
        glBindTexture(GL_TEXTURE_2D, tex_ID);
    }

    void Texture2D::update_data(const glm::ivec2 &offset, const glm::ivec2 &size, const uint8_t* data) {
        bind();
        if (offset.x + size.x <= width && offset.y + size.y <= height) {
            glTexSubImage2D(GL_TEXTURE_2D, 0, offset.x, offset.y, size.x, size.y, GL_RGB, GL_UNSIGNED_BYTE, data);
            glGenerateMipmap(GL_TEXTURE_2D);
        }
    }

    // Ref<Texture2D> Texture2D::Create(Ref<il::MImage> meta_image) {
    //     return MakeRef<Texture2D>(meta_image->width, meta_image->height, meta_image->channel, meta_image->data.get());
    // }

    // Ref<Texture2D> Texture2D::Create(int width, int height, int channel, const uint8_t* data) {
    //     return MakeRef<Texture2D>(width, height, channel, data);
    // }

    Ref<Texture2D> Texture2D::Create(TextureParameter param, Ref<il::MImage> meta_image) {
        Texture2DProp prop{
            meta_image->width,
            meta_image->height,
            meta_image->channel,
            param
        };
        return MakeRef<Texture2D>(prop, meta_image->data.get());
    }

    Ref<Texture2D> Texture2D::Create(Texture2DProp prop, const uint8_t* data) {
        return MakeRef<Texture2D>(prop, data);
    }

    const uint32_t TextureUnit::max_unit_num = GL_TEXTURE31 - GL_TEXTURE0;

    void TextureUnit::active(uint32_t index) {
        if (index > max_unit_num)
            return;
        glActiveTexture(GL_TEXTURE0 + index);
    }

}